package com.pcbly.web.components.utils.property;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

@SuppressWarnings("unchecked")
public class XMLOfficeProperties extends AbstractOfficeProperties implements
		OfficeProperties {
	private File file;
	private Document doc;
	private Map propertyCache;
	private final static Logger log = LogManager
			.getLogger(XMLOfficeProperties.class);

	public XMLOfficeProperties(InputStream in) throws Exception {
		propertyCache = new HashMap();
		Reader reader = new BufferedReader(new InputStreamReader(in));
		buildDoc(reader);
	}

	public XMLOfficeProperties(String fileName) throws IOException {
		propertyCache = new HashMap();
		file = new File(fileName);
		if (!file.exists()) {
			File tempFile = new File(file.getParentFile(),
					(new StringBuilder()).append(file.getName()).append(".tmp")
							.toString());
			if (tempFile.exists()) {
				log
						.error((new StringBuilder())
								.append("WARNING: ")
								.append(fileName)
								.append(" was not found, but temp file from ")
								.append(
										"previous write operation was. Attempting automatic recovery. Please ")
								.append("check file for data consistency.")
								.toString());
				tempFile.renameTo(file);
			} else {
				throw new FileNotFoundException((new StringBuilder()).append(
						"XML properties file does not exist: ")
						.append(fileName).toString());
			}
		} else {
			File tempFile = new File(file.getParentFile(),
					(new StringBuilder()).append(file.getName()).append(".tmp")
							.toString());
			if (tempFile.exists()) {
				log
						.error((new StringBuilder())
								.append("WARNING: found a temp file: ")
								.append(tempFile.getName())
								.append(". This may ")
								.append(
										"indicate that a previous write operation failed. Attempting automatic ")
								.append("recovery. Please check file ").append(
										fileName).append(
										" for data consistency.").toString());
				if (tempFile.lastModified() > file.lastModified()) {
					boolean error = false;
					Reader reader = null;
					try {
						reader = new InputStreamReader(new FileInputStream(
								tempFile), "UTF-8");
						SAXReader xmlReader = new SAXReader();
						xmlReader.read(reader);
					} catch (Exception e) {
						error = true;
					} finally {
						try {
							reader.close();
						} catch (Exception e) {
						}
					}
					if (error) {
						String bakFile = (new StringBuilder()).append(
								tempFile.getName()).append("-").append(
								System.currentTimeMillis()).append(".bak")
								.toString();
						tempFile.renameTo(new File(tempFile.getParentFile(),
								bakFile));
					} else {
						String bakFile = (new StringBuilder()).append(
								file.getName()).append("-").append(
								System.currentTimeMillis()).append(".bak")
								.toString();
						file.renameTo(new File(file.getParentFile(), bakFile));
						try {
							Thread.sleep(100L);
						} catch (Exception e) {
						}
						tempFile.renameTo(file);
					}
				} else {
					boolean error = false;
					Reader reader = null;
					try {
						reader = new InputStreamReader(
								new FileInputStream(file), "UTF-8");
						SAXReader xmlReader = new SAXReader();
						xmlReader.read(reader);
					} catch (Exception e) {
						error = true;
					} finally {
						try {
							reader.close();
						} catch (Exception e) {
						}
					}
					if (error) {
						String bakFileName = (new StringBuilder()).append(
								file.getName()).append("-").append(
								System.currentTimeMillis()).append(".bak")
								.toString();
						File bakFile = new File(file.getParentFile(),
								bakFileName);
						file.renameTo(bakFile);
						try {
							Thread.sleep(100L);
						} catch (Exception e) {
						}
						tempFile.renameTo(file);
					} else {
						String bakFile = (new StringBuilder()).append(
								tempFile.getName()).append("-").append(
								System.currentTimeMillis()).append(".bak")
								.toString();
						tempFile.renameTo(new File(tempFile.getParentFile(),
								bakFile));
					}
				}
			}
		}
		if (!file.canRead())
			throw new IOException((new StringBuilder()).append(
					"XML properties file must be readable: ").append(fileName)
					.toString());
		if (!file.canWrite())
			throw new IOException((new StringBuilder()).append(
					"XML properties file must be writable: ").append(fileName)
					.toString());
		Reader reader = null;
		try {
			reader = new InputStreamReader(new FileInputStream(file), "UTF-8");
			buildDoc(reader);
		} catch (Exception e) {
			log.error((new StringBuilder()).append(
					"Error creating XML properties file ").append(fileName)
					.append(": ").append(e.getMessage()).toString());
			throw new IOException(e.getMessage());
		} finally {
			try {
				reader.close();
			} catch (Exception e) {
				log.debug(e.getMessage(), e);
			}
		}
	}

	public String get(Object o) {
		String name;
		String value;
		Element element;
		name = (String) o;
		value = (String) propertyCache.get(name);
		if (value != null)
			return value;
		String propName[] = parsePropertyName(name);
		element = doc.getRootElement();
		for (int i = 0; i < propName.length; i++) {
			element = element.element(propName[i]);
			if (element == null)
				return null;
		}

		value = element.getText();
		if ("".equals(value))
			return null;
		value = value.trim();
		propertyCache.put(name, value);
		return value;
	}

	public Collection getChildrenNames(String parent) {
		String propName[] = parsePropertyName(parent);
		Element element = doc.getRootElement();
		for (int i = 0; i < propName.length; i++) {
			element = element.element(propName[i]);
			if (element == null)
				return Collections.emptyList();
		}

		List children = element.elements();
		int childCount = children.size();
		List childrenNames = new ArrayList(childCount);
		Element e;
		for (Iterator i$ = children.iterator(); i$.hasNext(); childrenNames
				.add(e.getName()))
			e = (Element) i$.next();

		return childrenNames;
	}

	public Collection getPropertyNames() {
		List propNames = new LinkedList();
		List elements = doc.getRootElement().elements();
		if (elements.size() == 0)
			return Collections.emptyList();
		Element element;
		for (Iterator i$ = elements.iterator(); i$.hasNext(); getElementNames(
				propNames, element, element.getName()))
			element = (Element) i$.next();

		return propNames;
	}

	public String getAttribute(String name, String attribute) {
		if (name == null || attribute == null)
			return null;
		String propName[] = parsePropertyName(name);
		Element element = doc.getRootElement();
		int i = 0;
		do {
			if (i >= propName.length)
				break;
			String child = propName[i];
			element = element.element(child);
			if (element == null)
				break;
			i++;
		} while (true);
		if (element != null)
			return element.attributeValue(attribute);
		else
			return null;
	}

	private void getElementNames(List list, Element e, String name) {
		if (e.elements().isEmpty()) {
			list.add(name);
		} else {
			List children = e.elements();
			for (int i = 0; i < children.size(); i++) {
				Element child = (Element) children.get(i);
				getElementNames(list, child, (new StringBuilder()).append(name)
						.append('.').append(child.getName()).toString());
			}

		}
	}

	public synchronized String put(String k, String v) {
		String name = k;
		String value = v;
		propertyCache.put(name, value);
		String propName[] = parsePropertyName(name);
		Element element = doc.getRootElement();
		for (int i = 0; i < propName.length; i++) {
			if (element.element(propName[i]) == null)
				element.addElement(propName[i]);
			element = element.element(propName[i]);
		}

		element.setText(value);
		saveProperties();
		return null;
	}

	public synchronized void putAll(Map propertyMap) {
		String propertyName;
		String propertyValue;
		for (Iterator i$ = propertyMap.entrySet().iterator(); i$.hasNext(); propertyCache
				.put(propertyName, propertyValue)) {
			java.util.Map.Entry entry = (java.util.Map.Entry) i$.next();
			propertyName = (String) entry.getKey();
			propertyValue = (String) entry.getValue();
			String propName[] = parsePropertyName(propertyName);
			Element element = doc.getRootElement();
			for (int i = 0; i < propName.length; i++) {
				if (element.element(propName[i]) == null)
					element.addElement(propName[i]);
				element = element.element(propName[i]);
			}

			if (propertyValue != null)
				element.setText(propertyValue);
		}

		saveProperties();
	}

	public synchronized String remove(Object n) {
		String name = (String) n;
		propertyCache.remove(name);
		String propName[] = parsePropertyName(name);
		Element element = doc.getRootElement();
		for (int i = 0; i < propName.length - 1; i++) {
			element = element.element(propName[i]);
			if (element == null)
				return null;
		}

		String value = element.getText();
		element.remove(element.element(propName[propName.length - 1]));
		saveProperties();
		return value;
	}

	public String getProperty(String name) {
		return get(name);
	}

	public boolean containsKey(Object object) {
		return get(object) != null;
	}

	public boolean containsValue(Object object) {
		throw new UnsupportedOperationException(
				"Not implemented in xml version");
	}

	public Collection values() {
		throw new UnsupportedOperationException(
				"Not implemented in xml version");
	}

	public boolean isEmpty() {
		return false;
	}

	public int size() {
		throw new UnsupportedOperationException(
				"Not implemented in xml version");
	}

	public Set entrySet() {
		throw new UnsupportedOperationException(
				"Not implemented in xml version");
	}

	public void clear() {
		throw new UnsupportedOperationException(
				"Not implemented in xml version");
	}

	public Set keySet() {
		throw new UnsupportedOperationException(
				"Not implemented in xml version");
	}

	public File getFile() {
		return file;
	}

	private void buildDoc(Reader in) throws Exception {
		SAXReader xmlReader = new SAXReader();
		doc = xmlReader.read(in);
	}

	private synchronized void saveProperties() {
		Logger log = LogManager.getLogger(XMLOfficeProperties.class);
		Writer writer = null;
		boolean error = false;
		File tempFile = null;
		try {
			tempFile = new File(file.getParentFile(), (new StringBuilder())
					.append(file.getName()).append(".tmp").toString());
			writer = new OutputStreamWriter(new FileOutputStream(tempFile),
					"UTF-8");
			XMLWriter xmlWriter = new XMLWriter(writer, OutputFormat
					.createPrettyPrint());
			xmlWriter.write(doc);
		} catch (Exception e) {
			log.error((new StringBuilder()).append("Unable to write to file ")
					.append(file.getName()).append(".tmp").append(": ").append(
							e.getMessage()).toString());
			error = true;
		} finally {
			try {
				writer.flush();
				writer.close();
			} catch (Exception e) {
				log.error(e);
				error = true;
			}
		}
		if (!error) {
			error = false;
			if (file.exists() && !file.delete()) {
				log.error((new StringBuilder()).append(
						"Error deleting property file: ").append(
						file.getAbsolutePath()).toString());
				return;
			}
			try {
				writer = new OutputStreamWriter(new FileOutputStream(file),
						"UTF-8");
				XMLWriter xmlWriter = new XMLWriter(writer, OutputFormat
						.createPrettyPrint());
				xmlWriter.write(doc);
			} catch (Exception e) {
				log.error((new StringBuilder()).append(
						"Unable to write to file '").append(file.getName())
						.append("': ").append(e.getMessage()).toString());
				error = true;
				try {
					file.delete();
				} catch (Exception fe) {
				}
			} finally {
				try {
					writer.flush();
					writer.close();
				} catch (Exception e) {
					log.error(e);
					error = true;
				}
			}
			if (!error)
				tempFile.delete();
		}
	}

	private String[] parsePropertyName(String name) {
		return StringUtils.split(name, '.');
	}

	public Collection getChildrenNames(Object x0) {
		return getChildrenNames((String) x0);
	}

	public Object put(Object x0, Object x1) {
		return put((String) x0, (String) x1);
	}

}
